<?php
/**
 * 	Bushido
 *
 * @category   Bushido
 * @package    Bushido_FusionCharts
 * @copyright  Copyright (c) 2008-2009 Nathan Keyes
 * @author Nathan Keyes
 * @version    $Id: Chart.php 7 2009-11-14 22:42:08Z nkeyes $
 */

/**
 * 
 * @author Nathan Keyes
 *
 */
class Bushido_FusionCharts_Chart implements Bushido_Configurable_Interface
{
	protected $allowedConfigOptions;
	protected $_datagram;
	protected $_chartNode;
	protected $_theme;
	protected $_config;
	
	protected $_attributes = array();
	
	protected $_trendLines = array();
	protected $_vtrendLines = array();
	
	
	/**
	 * 
	 * @param $datagram
	 * @param $configOptions
	 * @return void
	 */
	public function __construct(Bushido_Datagram_Abstract $datagram = null, $configOptions = array())
	{
		$this->allowedConfigOptions = array('id', 'caption', 'theme', 'chartWidth', 'chartHeight', 'palette', 'debug');
		$this->_config = new Bushido_Configurable();
		$this->getConfig()->addAllowedConfigOptions($this->allowedConfigOptions);
		$this->getConfig()->setConfigOptions($configOptions);

		$this->setDatagram($datagram);

		
		$themeName = isset($configOptions['theme']) ? $configOptions['theme'] : 'Default';
		
		$this->setThemeByName($themeName);
		
		if($this->getDatagram())
		{
			$this->resetData();
		}
		
	}
	
	
	/**
	 * 
	 * @return Bushido_Datagram_Abstract
	 */
	public function getDatagram()
	{
		return $this->_datagram;
	}
	
	
	/**
	 * 
	 * @param $datagram
	 * @return void
	 */
	public function setDatagram($datagram)
	{
		if($datagram)
		{
			$this->_datagram = $datagram;
			$this->_setChartNode(Bushido_FusionCharts::getChartXML($this->getDatagram(), $this->getConfig()->getConfigOptions(), $this->getTheme()));
		}
	}
	
	
	/**
	 * (non-PHPdoc)
	 * @see library/Bushido/Configurable/Bushido_Configurable_Interface#getConfig()
	 */
	public function getConfig()
	{
		return $this->_config;
	}
	
	/**
	 * 
	 * @return Bushido_FusionCharts_Theme_Abstract
	 */
	public function getTheme()
	{
		return $this->_theme;
	}
	
	
	/**
	 * 
	 * @param $theme
	 * @return void
	 */
	public function setTheme($theme)
	{
		$this->_theme = $theme;
	}
	
	
	/**
	 * 
	 * @param $themeName
	 * @return void
	 */
	public function setThemeByName($themeName)
	{
		$this->setTheme(Bushido_FusionCharts_Theme::factory($themeName));
	}
	
	
	/**
	 * 
	 * @param $xml
	 * @return void
	 */
	protected function _setChartNode($xml)
	{
		$this->_chartNode = simplexml_load_string($xml);
	}
	
	
	/**
	 * 
	 * @return string
	 */
	public function __toString()
	{
		$this->resetData();
		return Bushido_FusionCharts::getChart($this->_chartNode->asXML(), $this->getConfig()->getConfigOptions(), $this->getTheme());
	}
	/**
	 * 
	 * @return string
	 */
	public function getChartXML()
	{
		$this->resetData();
		return $this->_chartNode->asXML();
	}
	/**
	 * 
	 * @param $attributeName
	 * @param $attributeValue
	 * @return void
	 */
	public function setChartAttribute($attributeName, $attributeValue)
	{
		@$this->_chartNode->addAttribute($attributeName, $attributeValue); // suppress warnings about existing attributes
		$this->_attributes['chart'][$attributeName] = $attributeValue;
	}
	
	/**
	 * 
	 * @param $seriesName
	 * @param $attributeName
	 * @param $attributeValue
	 * @return void
	 */
	public function setSeriesAttribute($seriesName, $attributeName, $attributeValue)
	{
		$seriesNode = current($this->_chartNode->xpath("//dataset[@SeriesName='${seriesName}']"));
		@$seriesNode->addAttribute($attributeName, $attributeValue); // suppress warnings about existing attributes
		$this->_attributes['series'][$seriesName][$attributeName] = $attributeValue;
	}
	
	
	/**
	 * 
	 * @param $seriesName
	 * @param $dataPointName
	 * @param $attributeName
	 * @param $attributeValue
	 * @return void
	 */
	public function setSeriesDataPointAttribute($seriesName, $dataPointName, $attributeName, $attributeValue)
	{
		$setNode = current($this->_chartNode->xpath("//dataset[@SeriesName='${seriesName}']/set[@label='${dataPointName}']"));
		@$setNode->addAttribute($attributeName, $attributeValue); // suppress warnings about existing attributes
		$this->_attributes['dataPoint'][$seriesName][$dataPointName][$attributeName] = $attributeValue;
	}
	
	
	/**
	 * 
	 * @param $dataPointName
	 * @param $attributeName
	 * @param $attributeValue
	 * @return void
	 */
	public function setDataPointAttribute($dataPointName, $attributeName, $attributeValue)
	{
		$setNodes = $this->_chartNode->xpath("//set[@label='${dataPointName}']");
		foreach($setNodes as $setNode)
		{
			@$setNode->addAttribute($attributeName, $attributeValue); // suppress warnings about existing attributes
		}
		
		$this->_attributes['allDataPoints'][$dataPointName][$attributeName] = $attributeValue;
	}
	
	
	/**
	 * 
	 * @return void
	 */
	public function resetData()
	{
		$this->_setChartNode(Bushido_FusionCharts::getChartXML($this->getDatagram(), $this->getConfig()->getConfigOptions(), $this->getTheme()));
		$this->_resetAttributes();
	}
	
	
	/**
	 * 
	 * @return void
	 */
	protected function _resetTrendLines()
	{		
		foreach($this->_trendLines as $name => $trendLine)
		{
			$this->_addTrendLine($name, $trendLine['start'], $trendLine['end'], $trendLine['color'], $trendLine['isZone']);
		}
		
		foreach($this->_vtrendLines as $name => $trendLine)
		{
			$this->_addVTrendLine($name, $trendLine['start'], $trendLine['end'], $trendLine['color'], $trendLine['isZone']);
		}
	}
	
	
	/**
	 * 
	 * @return void
	 */
	protected function _resetAttributes()
	{
		if(isset($this->_attributes['chart']))
		{
			foreach($this->_attributes['chart'] as $attributeName => $attributeValue)
			{
				$this->setChartAttribute($attributeName, $attributeValue);
			}
		}
		
		if(isset($this->_attributes['series']))
		{
			foreach($this->_attributes['series'] as $series => $seriesAttributes)
			{
				foreach($seriesAttributes as $attributeName => $attributeValue)
				{
					$this->setSeriesAttribute($series, $attributeName, $attributeValue);
				}
			}
		}
		
		if(isset($this->_attributes['allDataPoints']))
		{
			foreach($this->_attributes['allDataPoints'] as $dataPoint => $dataPointAttributes)
			{
				foreach($dataPointAttributes as $attributeName => $attributeValue)
				{
					$this->setDataPointAttribute($dataPoint, $attributeName, $attributeValue);
				}
			}
		}
		
		if(isset($this->_attributes['dataPoint']))
		{
			foreach($this->_attributes['dataPoint'] as $series => $seriesDataPoints)
			{
				foreach($seriesDataPoints as $seriesDataPoint => $seriesDataPointAttributes)
				{
					foreach($seriesDataPointAttributes as $attributeName => $attributeValue)
					{
						$this->setSeriesDataPointAttribute($series, $seriesDataPoint, $attributeName, $attributeValue);
					}
				}
			}
		}
		if(isset($this->_attributes['trendLines']) && count($this->_trendLines) > 0)
		{
			$this->_resetTrendLines();
			foreach($this->_attributes['trendLines'] as $trendLine => $trendLineAttributes)
			{
				foreach($trendLineAttributes as $attributeName => $attributeValue)
				{
					$this->setTrendLineAttribute($trendLine, $attributeName, $attributeValue);
				}
			}
		}
	}
	
	/**
	 * 
	 * @param $seriesName
	 * @param $color
	 * @return void
	 */
	public function setSeriesTrendLine($seriesName, $color = null)
	{
		
		$setNodes = $this->_chartNode->xpath("//dataset[@SeriesName='${seriesName}']/set");
		$x= array();
		$y = array();
		
		
		for($i = 0; $i < count($setNodes); $i++)
		{
			$x[] = $i;
			$y[] = '' . current($setNodes[$i]->xpath('self::set/@value'));
		}
		
		$slopeIntercept = Bushido_Utils::linear_regression($x, $y);
		$start = $slopeIntercept['b'];
		$end = (count($setNodes) * $slopeIntercept['m']) + $slopeIntercept['b'];
		
		$min = $start < $end ? $start : $end;
		$max = $start > $end ? $start : $end;
		
		
		if($min < 0)
		{
			$min = floor($min);
			$this->setChartAttribute('yAxisMinValue', $min);
		}
		elseif($max > max($y))
		{
			$max = ceil($max);
			$this->setChartAttribute('yAxisMaxValue', $max);
		}
		$color = $color ? $color : '000000';
		
		$this->setTrendLine("$seriesName Trend", $start, $end, $color, 0);
	}
	
	/**
	 * 
	 * @param $seriesName
	 * @return void
	 */
	public function setSeriesRunningTotal($seriesName)
	{
		
		$setNodes = $this->_chartNode->xpath("//dataset[@SeriesName='${seriesName}']/set");
		$x= array();
		$setValues = array();
		$y = array();
		
		$runningTotalSeriesName = "$seriesName Running Total";
		$runningTotalSeriesDataPoints = array();
		
		
		foreach($setNodes as $setNode)
		{
			$setValues[] = '' . current($setNode->xpath('self::set/@value'));
			$runningTotalSeriesDataPoints['' . current($setNode->xpath('self::set/@label'))] = array_sum($setValues);
		}
		
		
		$this->setSeries($runningTotalSeriesName, $runningTotalSeriesDataPoints);
		
	}
	
	
	/**
	 * 
	 * @param $seriesName
	 * @return void
	 */
	public function setSeriesRunningAverage($seriesName)
	{
		
		$setNodes = $this->_chartNode->xpath("//dataset[@SeriesName='${seriesName}']/set");
		$x= array();
		$setValues = array();
		$y = array();
		
		$runningAverageSeriesName = "$seriesName Running Average";
		$runningAverageSeriesDataPoints = array();
		
		
		foreach($setNodes as $setNode)
		{
			$setValues[] = '' . current($setNode->xpath('self::set/@value'));
			$runningAverageSeriesDataPoints['' . current($setNode->xpath('self::set/@label'))] = round(array_sum($setValues)/count($setValues), 2);
		}
		
		
		$this->setSeries($runningAverageSeriesName, $runningAverageSeriesDataPoints);
	}
	
	
	/**
	 * 
	 * @param $name
	 * @return void
	 */
	protected function _addTrendLine($name)
	{
		$trendLinesNode = $this->_chartNode->xpath('//trendLines');
		if(!$trendLinesNode)
		{
			$trendLinesNode = simplexml_load_string("<trendLines />");			
			$lineNode = simplexml_load_string("<line displayvalue='$name' />");
			
			Bushido_Utils::simplexml_append($trendLinesNode, $lineNode);
			
			Bushido_Utils::simplexml_append($this->_chartNode, $trendLinesNode);
			return;
		}
		$trendLinesNode = current($trendLinesNode);

		$lineNode = simplexml_load_string("<line displayvalue='$name' />");
		
		Bushido_Utils::simplexml_append($trendLinesNode, $lineNode);
		
		
		
		
	}
	
	
	/**
	 * 
	 * @param $name
	 * @return void
	 */
	protected function _addVTrendLine($name)
	{
		$trendLinesNode = $this->_chartNode->xpath('//vTrendLines');
		if(!$trendLinesNode)
		{
			$trendLinesNode = simplexml_load_string("<vTrendLines />");			
			$lineNode = simplexml_load_string("<line displayvalue='$name' />");
			
			Bushido_Utils::simplexml_append($trendLinesNode, $lineNode);
			
			Bushido_Utils::simplexml_append($this->_chartNode, $trendLinesNode);
			return;
		}
		$trendLinesNode = current($trendLinesNode);

		$lineNode = simplexml_load_string("<line displayvalue='$name' />");
		
		Bushido_Utils::simplexml_append($trendLinesNode, $lineNode);
		
		
		
		
	}
	
	
	/**
	 * 
	 * @param $name
	 * @param $start
	 * @param $end
	 * @param $color
	 * @param $isZone
	 * @return void
	 */
	public function setTrendLine($name, $start, $end, $color, $isZone = 0)
	{
		$this->_trendLines[$name] = array
		(
			'start' => $start,
			'end' => $end,
			'color' => $color,
			'isZone' => $isZone
		);
		$this->_addTrendLine($name);
		$this->setTrendLineAttribute($name, 'startValue', $start);
		$this->setTrendLineAttribute($name, 'endValue', $end);
		$this->setTrendLineAttribute($name, 'color', $color);
		$this->setTrendLineAttribute($name, 'isTrendZone', $isZone);
	}
	
	/**
	 * 
	 * @param $name
	 * @param $start
	 * @param $end
	 * @param $color
	 * @param $isZone
	 * @return void
	 */
	public function setVTrendLine($name, $start, $end, $color, $isZone = 0)
	{
		$this->_vtrendLines[$name] = array
		(
			'start' => $start,
			'end' => $end,
			'color' => $color,
			'isZone' => $isZone
		);
		$this->_addVTrendLine($name);
		$this->setVTrendLineAttribute($name, 'startValue', $start);
		$this->setVTrendLineAttribute($name, 'endValue', $end);
		$this->setVTrendLineAttribute($name, 'color', $color);
		$this->setVTrendLineAttribute($name, 'isTrendZone', $isZone);
	}
	
	
	/**
	 * 
	 * @param $trendLineName
	 * @param $attributeName
	 * @param $attributeValue
	 * @return void
	 */
	public function setTrendLineAttribute($trendLineName, $attributeName, $attributeValue)
	{
		$this->_attributes['trendLines'][$trendLineName][$attributeName] = $attributeValue;
		$trendLineNode = current($this->_chartNode->xpath("//trendLines/line[@displayvalue='${trendLineName}']"));
		@$trendLineNode->addAttribute($attributeName, $attributeValue); // suppress warnings about existing attributes
	}
	
	/**
	 * 
	 * @param $trendLineName
	 * @param $attributeName
	 * @param $attributeValue
	 * @return void
	 */
	public function setVTrendLineAttribute($trendLineName, $attributeName, $attributeValue)
	{
		$this->_attributes['vtrendLines'][$trendLineName][$attributeName] = $attributeValue;
		$trendLineNode = current($this->_chartNode->xpath("//vTrendLines/line[@displayvalue='${trendLineName}']"));
		@$trendLineNode->addAttribute($attributeName, $attributeValue); // suppress warnings about existing attributes
	}
	
	
	/**
	 * 
	 * @param $name
	 * @return void
	 */
	public function deleteTrendLine($name)
	{
		if(isset($this->_trendLines[$name]))
		{
			unset($this->_trendLines[$name]);
		}
	}
	
	
	/**
	 * 
	 * @param $name
	 * @return void
	 */
	public function deleteVTrendLine($name)
	{
		if(isset($this->_vtrendLines[$name]))
		{
			unset($this->_vtrendLines[$name]);
		}
	}
	
	/**
	 * 
	 * @param $name
	 * @param $arguments
	 * @return mixed
	 */
	public function __call($name, $arguments)
	{
		if(method_exists($this->_chartNode, $name))
		{
			$targetObj = $this->_chartNode;
		}
		elseif(method_exists($this->getDatagram(), $name))
		{
			$targetObj = $this->getDatagram();
		}
		else
		{
			throw new Exception(sprintf('Function "%s" does not exist and was not trapped in __call()', $name));
		}
		
		$retVal = call_user_func_array(array($targetObj, $name), $arguments);
		$this->resetData();
		
		return $retVal;
	}
	
}